import { Injectable, ComponentRef } from '@angular/core';
import { ComponentPortal } from '@angular/cdk/portal';
import { Overlay, OverlayRef } from '@angular/cdk/overlay';
import { isEmpty } from '../core/utils/common';
import { ModalModule } from '../modal/modal.module';
import { ModalRef } from '../modal/modalRef';
import { ModalComponent } from '../modal/modal.component';
import { ModalContainerService } from '../modal/modal-container.service';
import { ModalTypeEnum, genWidthByDrawEnum } from './interface';

export class ModalServiceBuilder {
  private overlayRef: OverlayRef;
  private modalRef: ComponentRef<ModalComponent<any>> | null;

  constructor(modalService, options) {
    this.createModal(modalService, options);
  }

  getInstance(): ModalComponent<any> | null {
    return this.modalRef?.instance;
  }

  destroyModal(modalService, options): void {
    // Destroy modal container
    if (this.modalRef) {
      modalService.modalContainer.modals = modalService.modalContainer.modals.filter(
        (item): boolean => item.key !== options.lvModalKey,
      );

      this.overlayRef.dispose();
      this.modalRef = null;
    }
  }

  createModal(modalService, options): void {
    // Create a modal container
    this.overlayRef = modalService.overlay.create();
    this.modalRef = this.overlayRef.attach(new ComponentPortal(ModalComponent));

    // Display modal
    Object.assign(this.modalRef.instance, options);
    this.modalRef.instance.open();

    const closeSubscribe = this.modalRef?.instance.openState$.subscribe((open): void => {
      if (!open) {
        this.destroyModal(modalService, options);
        closeSubscribe.unsubscribe();
      }
    });
  }
}

@Injectable({ providedIn: ModalModule })
export class ModalService {
  constructor(
    private modalContainer: ModalContainerService,
    private overlay: Overlay,
  ) {}

  // Expose ModalRef[] through ModalService.
  public get modals(): Array<{ key: string; modal: ModalComponent<any> }> {
    return this.modalContainer.modals;
  }

  destroyAllModals(): void {
    this.modalContainer.modals.forEach((item): void => {
      item.modal.close();
    });
    this.modalContainer.modals = [];
  }

  getModal(key): any {
    const modalMap = this.modalContainer.modals.find((item): boolean => item.key === key);
    return modalMap ? modalMap.modal : modalMap;
  }

  destroyModal(key): void {
    this.modalContainer.modals.forEach((item): void => {
      if (key === item.key) {
        item.modal.close();
      }
    });

    const newModals = this.modalContainer.modals.filter((item): boolean => key !== item.key);
    this.modalContainer.modals = newModals;
  }

  // Create and return a ModalRef instance
  create(options): ModalRef {
    let exist = false;

    // If modal set default width
    if ((!('lvType' in options) || options.lvType === ModalTypeEnum.MODAL) && !('lvWidth' in options)) {
      options.lvWidth = 800;
    }

    // If drawer scene, 4 enums of width can be set
    genWidthByDrawEnum(options);

    // If popover set default modality
    if (!('lvModality' in options) && options.lvType === ModalTypeEnum.POPOVER) {
      options.lvModality = false;
    }

    if (options.lvModalKey) {
      this.modalContainer.modals.forEach((item): void => {
        if (item.key === options.lvModalKey) {
          exist = true;
        }
      });
    } else {
      options.lvModalKey = this._generateId();
    }

    if (!exist) {
      const modal = new ModalServiceBuilder(this, options).getInstance();
      this.modalContainer.modals.push({
        key: options.lvModalKey,
        modal,
      });
      return modal;
    } else {
      return null;
    }
  }

  update(modalKey, options): void {
    let exist = false;
    let modal;

    this.modalContainer.modals.forEach((item): void => {
      if (item.key === modalKey) {
        exist = true;
        ({ modal } = item);
      }
    });

    if (exist) {
      const posMap = {
        left: 'drawerLeftIn',
        right: 'drawerRightIn',
        top: 'drawerTopIn',
        bottom: 'drawerBottomIn',
      };
      options.lvDrawerPosition && (modal._animationState = posMap[options.lvDrawerPosition]); // 位置变更时更新抽屉的动画初始状态

      if (options.lvContent && modal.portalHost && !modal.portalHost._isDisposed) {
        modal.portalHost.detach();
      }

      Object.assign(modal, options);
      if (options.lvContent) {
        modal.setComponentAsContent();
      }

      if (options.lvComponentParams) {
        modal.setComponentParams();
      }

      modal.setZIndex();
      modal.setSizeAndPosition();
      modal.updatePopoverPos();
      if (options.lvFooter) {
        modal.setFooter();
      }

      modal.isNoHeader = isEmpty(modal.lvHeader);
      modal.isNoFooter = isEmpty(modal.lvFooter);

      if (options.lvFocusButtonId) {
        setTimeout((): void => {
          modal.setFocus();
        }, 0);
      }
    }
  }

  // 32 random character strings as id
  private _generateId(): string {
    return 'xyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxy'.replace(/[xy]/g, (c): string => {
      // tslint:disable-next-line
      const r = (Math.random() * 16) | 0;
      // tslint:disable-next-line
      const v = c === 'x' ? r : (r & 0x3) | 0x8;
      return v.toString(16);
    });
  }
}
